package cn.cas.mango.service;

import cn.cas.mango.dto.Message;
import cn.cas.mango.dto.News;
import cn.cas.mango.dto.param.BaseParam;
import cn.cas.mango.dto.param.DetailParam;
import cn.cas.mango.dto.param.DetailSimParam;
import cn.cas.mango.dto.param.QueryParam;
import cn.cas.mango.util.EsTypeTransUtil;
import cn.cas.mango.util.Pair;
import cn.cas.mango.util.RenderDateUtil;
import cn.cas.mango.util.RequestUtil.BuildRequest;
import cn.cas.mango.util.paramCheck.DetailParamCheckUtil;
import cn.cas.mango.util.ResponseUtil.ResponseUtil;
import cn.cas.mango.util.paramCheck.DetailSimParamCheckUtil;
import cn.cas.mango.util.paramCheck.QueryParamCheckUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DetailService extends BaseService {
  @Autowired
  private ElasticsearchClient elasticsearchClient;
  @Autowired
  private ConfigService config;
  @Value("${elasticsearch.newsDetail.pageSize}")
  private Integer detailPageSize;
  @Autowired
  private BuildRequest buildRequest;
  @Value("${postForKeywords.url}")
  private String postKeywordsUrl;
  private Set<String> blackList = new HashSet<>();
  {
      try {
          blackList.addAll(getBlackList1("blacklist.txt"));
      } catch (IOException e) {
          throw new RuntimeException(e);
      }
  }
  public List<String> getBlackList1(String filePath) throws IOException {
    FileInputStream fis = new FileInputStream(filePath);
    List<String> blackLists = new ArrayList<>();
    //Construct BufferedReader from InputStreamReader
    BufferedReader br = new BufferedReader(new InputStreamReader(fis));
    String line = null;
    while ((line = br.readLine()) != null) {
      blackLists.add(line.trim());
    }
    br.close();
    fis.close();
    return blackLists;
  }
  private final String[] includes = new String[]{"news_content_zh", "news_title_zh", "news_publictime", "news_url", "domain", "news_entities_organization", "news_type", "news_source", "news_website_type", "news_title", "news_content", "news_website", "title_content_vector", "news_media_name", "news_entities_place", "news_keywords", "news_author", "news_summary", "id", "news_publicdate", "news_language", "news_entities_person", "news_entities", "crawl_time"};
  public Message run(BaseParam baseParam) {
    DetailParam detailParam = (DetailParam) baseParam;
    Map<String, Object> errMessage = DetailParamCheckUtil.checkParam(detailParam);
    String isSuccess = (String) errMessage.get("isSuccess");
    if (!isSuccess.equals("ok")) {
      return Message.unSuccess(isSuccess);
    }
    List<Hit<News>> hits = null;
    try {
      String id = detailParam.getId();
      hits = searchById(id);
      if (hits.size() == 0) {
        isSuccess = "该id没有查询结果，请检查id";
        return Message.unSuccess(isSuccess);
      }
    } catch (IOException e) {
      log.error(e.toString());
    }
    assert hits != null;
    Hit<News> newsHit = hits.get(0);
    News news = newsHit.source();
    JSONObject link = new JSONObject();

    link.put("keywordsBy", "B");
    assert news != null;
    ResponseUtil.putCommon(config, link, newsHit);
    ResponseUtil.putCommon(link, news);
    link.put("news_content", news.getNewsContentZh());
    link.put("news_sourcesite", news.getNewsWebsite());
    link.put("news_summary", news.getNewsSummary());
    link.put("news_title", news.getNewsTitleZh());
    processKeywords(news, link);
    processEntities(news, link);
    return Message.builder().code(200).message("成功").data(link).build();
  }

  /**
   * 预览新闻
   * @param detailParam, 传递id
   * @return 预览结果
   */
  public Message preViewNews(DetailParam detailParam) {
    Map<String, Object> errMessage = DetailParamCheckUtil.checkParam(detailParam);
    String isSuccess = (String) errMessage.get("isSuccess");
    if (!isSuccess.equals("ok")) {
      return Message.unSuccess(isSuccess);
    }
    List<Hit<News>> hits = null;
    try {
      String id = detailParam.getId();
      hits = searchById(id);
      if (hits.size() == 0) {
        isSuccess = "该id没有查询结果，请检查id";
        return Message.unSuccess(isSuccess);
      }
    } catch (IOException e) {
      log.error(e.toString());
    }
    assert hits != null;
    Hit<News> newsHit = hits.get(0);
    News news = newsHit.source();
    JSONObject link = new JSONObject();


    assert news != null;
    ResponseUtil.putCommon(config, link, newsHit);
    ResponseUtil.putCommon(link, news);
    link.put("news_content", news.getNewsContentZh());
    link.put("news_sourcesite", news.getNewsWebsite());
    link.put("fromId", "");
    link.put("toId", "");
    link.put("news_summary", news.getNewsSummary());
    link.put("news_title", news.getNewsTitleZh());
    link.put("news_publictime_date", news.getNewsPublicDate());
    return Message.builder().code(200).message("成功").data(link).build();
  }

  /**
   * 获取相似新闻
   * @param param, 传递参数
   * @return 返回推荐的相似新闻
   */
  public Message getSimNews(BaseParam param) {
    DetailSimParam detailSimParam = (DetailSimParam) param;
    if (detailSimParam.getDegree() == null) {
      detailSimParam.setDegree(config.getSimDegree());
    }
    Map<String, Object> errMessage = DetailSimParamCheckUtil.checkParam(detailSimParam);

    String isSuccess = (String) errMessage.get("isSuccess");
    if (!isSuccess.equals("ok")) {
      return Message.unSuccess(isSuccess);
    }
    List<Hit<News>> hits = null;
    try {
      String id = detailSimParam.getId();
      hits = searchById(id);
      if (hits.size() == 0) {
        isSuccess = "该id没有查询结果，请检查id";
        return Message.unSuccess(isSuccess);
      }
    } catch (IOException e) {
      log.error(e.toString());
    }
    assert hits != null;
    Hit<News> newsHit = hits.get(0);
    News news = newsHit.source();
    QueryParam queryParam = wrapQueryParam(param, detailPageSize, news);
    SearchRequest.Builder builder = buildRequest.buildRequest(queryParam);
    SearchRequest build = builder.build();
    log.info("get sim news query: {}", build);
    JSONObject link;
    SearchResponse<News> search;
    try {
      search = elasticsearchClient.search(build, News.class);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    link = new JSONObject();
    link.put("totalResult", search.hits().total().value());
    List<JSONObject> list = new ArrayList<>();
    List<Hit<News>> hitNews = search.hits().hits();
    for (Hit<News> hit: hitNews) {
      News n = hit.source();
      assert n != null;
      JSONObject jsonObject = new JSONObject();
      jsonObject.put("news_title", n.getNewsTitleZh());
      jsonObject.put("news_publictime_date", n.getNewsPublicDate());
      jsonObject.put("news_website", n.getNewsWebsite());
      jsonObject.put("news_sourcesite", n.getNewsWebsite());
      jsonObject.put("id", n.getId());
//      jsonObject.put("news_content", n.getNewsContentZh());
      jsonObject.put("news_content", "");
      list.add(jsonObject);
    }
    link.put("recommendNews", list);
    return Message.builder().code(200).message("成功").data(link).build();
  }

  private String getSearchingKeywordsEntities(News news) {
    Set<String> searchingKeywords = new HashSet<>();
    assert news != null;
    if (config.getSimField().equals("all")) {
      List<String> keywords = processKeywords(news).stream().map(x -> (String) x.get("name")).collect(Collectors.toList());
      searchingKeywords.addAll(keywords.subList(0, Math.min(config.getRecommendKeywordsNum(), keywords.size())));
      searchingKeywords.addAll(EsTypeTransUtil.entity2List(news.getNewsEntities()));
    } else if (config.getSimField().startsWith("k")) {
      List<String> keywords = processKeywords(news).stream().map(x -> (String) x.get("name")).collect(Collectors.toList());
      searchingKeywords.addAll(keywords.subList(0, Math.min(config.getRecommendKeywordsNum(), keywords.size())));
    } else {
      searchingKeywords.addAll(EsTypeTransUtil.entity2List(news.getNewsEntities()));
    }
    List<String> ret = new ArrayList<>(searchingKeywords);
    if (!searchingKeywords.isEmpty()) {
      ret = ret.subList(0, Math.min(config.getRecommendKeywordsNum(), ret.size()));
    }

    return String.join(" ", ret);
  }

  private QueryParam wrapQueryParam(BaseParam baseParam, Integer detailPageSize, News news) {
    DetailSimParam detailSimParam = (DetailSimParam) baseParam;
    String searchingKeywords = getSearchingKeywordsEntities(news);
    QueryParam queryParam = new QueryParam();

    queryParam.setStartDate(detailSimParam.getStartDate());
    queryParam.setEndDate(detailSimParam.getEndDate());

    queryParam.setCurrentPage(detailSimParam.getCurrentPage());
    if (detailSimParam.getPageSize() == null) {
      queryParam.setPageSize(10);
    } else {
      queryParam.setPageSize(detailSimParam.getPageSize());
    }
    queryParam.setQueryStr(searchingKeywords);
    queryParam.setSortType("sim");
    queryParam.setZh(true);
    queryParam.setSearchType("false");
    queryParam.setStartDate(detailSimParam.getStartDate());
    queryParam.setEndDate(detailSimParam.getEndDate());
    queryParam.setWebSites(detailSimParam.getWebSites());
    queryParam.setWebSiteType(detailSimParam.getWebSiteType());
    queryParam.setLikeDegree((int)(detailSimParam.getDegree() * 100) + "%");

    if (Strings.isBlank(queryParam.getStartDate()) || Strings.isBlank(queryParam.getEndDate())) {
      Pair<String, String> aYearStartEnd = RenderDateUtil.getRecent1Year();
      queryParam.setStartDate(aYearStartEnd.getKey());
      queryParam.setEndDate(aYearStartEnd.getValue());
    }
    QueryParamCheckUtil.checkParam(queryParam, detailPageSize);
    queryParam.setStartDate(null);
    queryParam.setEndDate(null);
    log.info("\n\nnow startDate: {}, endDate: {}\n", queryParam.getStartDate(), queryParam.getEndDate());
    return queryParam;
  }

  private void processKeywords(News news, JSONObject link) {

    link.put("keywords", processKeywords(news));
  }

  private List<Map> processKeywords(News news) {
    String content = (String)news.getNewsContentZh();
    content = Strings.isBlank(content) ? "": content;
    String title = (String) news.getNewsTitleZh();
    title = Strings.isBlank(title) ? "" : title;
    String text = title + " " + content;
    //  [
    //                        {"score": 0.532, "word": "中国"},
    //                        {"score": 0.532, "word": "美国"},
    //                        {"score": 0.532, "word": "加拿大"},
    //                        {"score": 0.532, "word": "俄罗斯"}
    //                    ]
    List<Map<String, Object>> originalKeywords = requestForKeywords(new ArrayList<String>() {{
      add(text);
    }});


    List<Map> maps = new ArrayList<>();

    for (Map<String, Object> keyword: originalKeywords) {
      maps.add(new HashMap<String, Object>(){{put("name", keyword.get("word"));put("value", keyword.get("score"));}});
    }
    maps = filterKeywords(maps);
//    link.put("news_keywords", keywords);
    return maps.subList(0, Math.min(config.getNewsDetailShowMaxKeywords(), maps.size()));

  }

  private List<Map> filterKeywords(List<Map> maps) {
    List<Map> ret = new ArrayList<>();
    List<Map> name = maps.stream().filter(x -> {
          String s = (String) x.get("name");
          return s.length() <= 7 && !containsLetter(s);
        }
    ).collect(Collectors.toList());
    for (Map n: name) {
      if (!blackList.contains(n.get("name"))) {
        ret.add(n);
      }
    }
//    return ret;
    return ret.subList(0, Math.min(config.getNewsDetailShowMaxKeywords(), ret.size()));
  }

  private static boolean containsLetter(String input) {
    for (char c : input.toCharArray()) {
      if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
        return true;
      }
    }
    return false;
  }

  private void processEntities(News news, JSONObject link) {
    Object newsEntities = news.getNewsEntities();
    List<String> entitiesList = getList(newsEntities);
    link.put("news_entities", entitiesList);
  }

  private List<String> getList(Object o) {
    List<String> finalRes = new ArrayList<>();
    if (o instanceof String) {
      finalRes.add((String) o);
    } else if (o instanceof List) {
      finalRes.addAll((List) o);
    }
    return finalRes;
  }


  private List<Map<String, Object>> requestForKeywords(List<String> texts) {
    JSONObject request = new JSONObject();
    request.put("logid", 123);
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("text_list", texts);
    request.put("body", jsonObject);

    RestTemplateBuilder builder = new RestTemplateBuilder();
    RestTemplate restTemplate = builder.build();
    String url = postKeywordsUrl;
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    HttpEntity<String> entity = new HttpEntity<>(request.toString(), headers);
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
    mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(
        MediaType.TEXT_HTML,
        MediaType.TEXT_PLAIN));
    restTemplate.getMessageConverters().add(mappingJackson2HttpMessageConverter);

    ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(url, entity,
        JSONObject.class);
    JSONObject body = responseEntity.getBody();
    Object o = body.getJSONObject("body").getJSONObject("results").getJSONArray("items").get(0);
    Object o1 = ((JSONObject) o).get("keywords");
    return (List<Map<String, Object>>) o1;
  }

  private List<Hit<News>> searchById(String id) throws IOException {
    SearchRequest query = SearchRequest.of(
        s -> s.index(config.getIndex())
            .query(
                q -> q.term(
                    e -> e.field("id").value(v -> v.stringValue(id))
                )
            ).size(10).source(source -> source.filter(f -> f.includes(Arrays.asList(includes))))
    );
    System.out.println(query);

    SearchResponse<News> ret = elasticsearchClient.search(
        query, News.class);
    List<Hit<News>> hits = ret.hits().hits();

    return ret.hits().hits();
  }
}
